package org.softee.functional.helper;

import java.util.Iterator;
import java.util.NoSuchElementException;

public class DelegatingIterables {
    public static abstract class FilteringIterable<T> implements Iterable<T> {
        private final Iterable<T> delegate;

        public FilteringIterable(Iterable<T> delegate) {
            this.delegate = delegate;
        }

        @Override
        public Iterator<T> iterator() {
            return new Iterator<T>() {
                Iterator<T> iterator = delegate.iterator();
                boolean hasPrefetch = false;
                T prefetch = null;

                @Override
                public boolean hasNext() {
                    return prefetch();
                }

                @Override
                public T next() {
                    if (prefetch()) {
                        hasPrefetch = false; // consume
                        T next = prefetch;
                        prefetch = null; // allow GC
                        return next;
                    }
                    throw new NoSuchElementException();
                }

                /**
                 * Ensures that hasPrefetch is updated and prefetch contains next accepted element
                 * @return false if iterator is exhausted with no more matching elements
                 */
                private boolean prefetch() {
                    if (hasPrefetch) {
                        return true;
                    }
                    // keep fetching source elements until match or exhausted
                    for (;;) {
                        if (!iterator.hasNext()) {
                            return false;
                        }
                        prefetch = iterator.next();
                        if (accept(prefetch)) {
                            hasPrefetch = true;
                            return hasPrefetch;
                        }
                    }
                }

                @Override
                public void remove() {
                    iterator.remove();
                }
            };
        }

        protected abstract boolean accept(final T input);
    }

    public static abstract class TransformingIterable<T, U> implements Iterable<U> {
        private final Iterable<T> delegate;

        public TransformingIterable(Iterable<T> delegate) {
            this.delegate = delegate;
        }

        @Override
        public Iterator<U> iterator() {
            return new Iterator<U>() {
                Iterator<T> iterator = delegate.iterator();

                @Override
                public boolean hasNext() {
                    return iterator.hasNext();
                }

                @Override
                public U next() {
                    return transform(iterator.next());
                }

                @Override
                public void remove() {
                    iterator.remove();
                }
            };
        }

        protected abstract U transform(final T input);
    }

    public static abstract class TransformingMultiIterable<T, U> implements Iterable<U> {
        private final Iterable<T> delegate;

        public TransformingMultiIterable(Iterable<T> delegate) {
            this.delegate = delegate;
        }

        @Override
        public Iterator<U> iterator() {
            return new Iterator<U>() {
                Iterator<T> iterator = delegate.iterator();
                Iterator<U> innerIterator = null;
                boolean hasPrefetch = false;
                U prefetch = null;

                @Override
                public boolean hasNext() {
                    return prefetch();
                }

                @Override
                public U next() {
                    if (prefetch()) {
                        hasPrefetch = false; // consume
                        U next = prefetch;
                        prefetch = null; // allow GC
                        return next;
                    }
                    throw new NoSuchElementException();
                }

                @Override
                public void remove() {
                    throw new UnsupportedOperationException();
                }

                /**
                 * Ensures that hasPrefetch is updated and prefetch contains next accepted element
                 * @return false if iterator is exhausted with no more matching elements
                 */
                private boolean prefetch() {
                    if (hasPrefetch) {
                        return true;
                    }
                    // keep fetching source elements until match or exhausted
                    for (;;) {
                        if (innerIterator == null || !innerIterator.hasNext()) {
                            if (!iterator.hasNext()) {
                                return false;
                            }
                            innerIterator = transform(iterator.next()).iterator();
                        }

                        if (innerIterator.hasNext()) {
                            prefetch = innerIterator.next();
                            hasPrefetch = true;
                            return hasPrefetch;
                        }

                        innerIterator = null; // mark as exhausted
                    }
                }

            };
        }

        protected abstract Iterable<U> transform(final T input);
    }

}
